﻿Option Strict On
Option Explicit On

Imports System.Drawing

''' <summary>
''' Provides methods and constants to assist with mathmatic calculations and physics in game logic.
''' </summary>
''' <remarks>
''' This class contains all of the standard methods from System.Math formatted for Single values instead of Double,
''' as well as several helper-methods to assist with object movement and physics calculations.
''' </remarks>
Public NotInheritable Class Geometry
    ''' <summary>
    ''' 0.0
    ''' </summary>
    ''' <remarks></remarks>
    Public Const ZERO As Single = 0.0F
    ''' <summary>
    ''' 1.0
    ''' </summary>
    ''' <remarks></remarks>
    Public Const ONE As Single = 1.0F
    ''' <summary>
    ''' 2.0
    ''' </summary>
    ''' <remarks></remarks>
    Public Const TWO As Single = 2.0F

    ''' <summary>
    ''' 180 Degrees
    ''' </summary>
    ''' <remarks></remarks>
    Public Const HALF_CIRCLE As Single = 180.0F
    ''' <summary>
    ''' 360 Degrees
    ''' </summary>
    ''' <remarks></remarks>
    Public Const FULL_CIRCLE As Single = 360.0F
    ''' <summary>
    ''' 90 Degrees
    ''' </summary>
    ''' <remarks></remarks>
    Public Const RIGHT_ANGLE As Single = 90.0F

    ''' <summary>
    ''' The value of PI (3.1415926)
    ''' </summary>
    ''' <remarks></remarks>
    Public Const PI As Single = CSng(Math.PI)
    ''' <summary>
    ''' The value of PI * 2
    ''' </summary>
    ''' <remarks></remarks>
    Public Const TWO_PI As Single = PI * TWO
    ''' <summary>
    ''' The value of PI squared.
    ''' </summary>
    ''' <remarks></remarks>
    Public Const PI_SQUARED As Single = PI * PI

    ''' <summary>
    ''' This class is sealed.
    ''' </summary>
    ''' <remarks></remarks>
    Protected Sub New()
    End Sub

    Public Shared Function Atan(ByVal value As Single) As Single
        Return CSng(Math.Atan(CDbl(value)))
    End Function

    Public Shared Function Atan2(ByVal y As Single, ByVal x As Single) As Single
        Return CSng(Math.Atan2(CDbl(y), CDbl(x)))
    End Function

    ''' <summary>
    ''' Ensures that the specified value falls between the specified min and max values. If value
    ''' is less than min, value becomes min; if value is greater than max, value becomes max.
    ''' </summary>
    ''' <param name="value">The value to clamp.</param>
    ''' <param name="min">The minimum acceptable value.</param>
    ''' <param name="max">The maximum acceptable value.</param>
    ''' <returns>The clamped value.</returns>
    ''' <remarks></remarks>
    Public Shared Function Clamp(ByVal value As Single, ByVal min As Single, ByVal max As Single) As Single
        Return Geometry.Max(min, Geometry.Min(value, max))
    End Function

    ''' <summary>
    ''' Converts an array of PointF to an array of Vector2.
    ''' </summary>
    ''' <param name="source">The PointF array to convert.</param>
    ''' <returns>The converted array of Vector2.</returns>
    ''' <remarks></remarks>
    Public Shared Function ConvertArrayPointFToVector2(ByVal source() As PointF) As Vector2()
        Return Array.ConvertAll(Of PointF, Vector2)(source, Function(p As PointF) New Vector2(p))
    End Function

    ''' <summary>
    ''' Converts an array of Vector2 to an array of PointF.
    ''' </summary>
    ''' <param name="source">The Vector2 array to convert.</param>
    ''' <returns>The converted array of PointF.</returns>
    ''' <remarks></remarks>
    Public Shared Function ConvertArrayVector2ToPointF(ByVal source() As Vector2) As PointF()
        Return Array.ConvertAll(Of Vector2, PointF)(source, Function(v As Vector2) v.ToPointF)
    End Function

    ''' <summary>
    ''' Gets the Cosine of an angle specified in radians.
    ''' </summary>
    ''' <param name="radians">The angle in radians.</param>
    ''' <returns>The Cosine of the angle.</returns>
    ''' <remarks></remarks>
    Public Shared Function Cos(ByVal radians As Single) As Single
        Return CSng(Math.Cos(CDbl(radians)))
    End Function

    ''' <summary>
    ''' Converts the specified angle from degrees to radians.
    ''' </summary>
    ''' <param name="degrees">The angle in degrees.</param>
    ''' <returns>The angle in radians.</returns>
    ''' <remarks></remarks>
    Public Shared Function DegreesToRadians(ByVal degrees As Single) As Single
        Return degrees * (PI / HALF_CIRCLE)
    End Function

    ''' <summary>
    ''' Determines if two values are equal within the specified tolerance.
    ''' </summary>
    ''' <param name="source">The first value to compare.</param>
    ''' <param name="target">The second value to compare.</param>
    ''' <param name="tolerance">The tolerance between values.</param>
    ''' <returns>True if the two values are equal within the tolerance, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Shared Function EqualWithin(ByVal source As Single, ByVal target As Single, ByVal tolerance As Single) As Boolean
        Return source - target <= tolerance
    End Function

    ''' <summary>
    ''' Gets the resulting velocity vector after two object collide, given their velocities and mass.
    ''' </summary>
    ''' <param name="velocityOne">The velocity vector of the first object.</param>
    ''' <param name="massOne">The mass of the first object.</param>
    ''' <param name="velocityTwo">The velocity vector of the second object.</param>
    ''' <param name="massTwo">The mass of the second object.</param>
    ''' <returns>The velocity resulting from the collision.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetCollisionVelocity(ByVal velocityOne As Vector2, ByVal massOne As Single, ByVal velocityTwo As Vector2, ByVal massTwo As Single) As Vector2
        Dim x As Single = (massOne * velocityOne.X + massTwo * velocityTwo.X) / (massOne + massTwo)
        Dim y As Single = (massOne * velocityOne.Y + massTwo * velocityTwo.Y) / (massOne + massTwo)
        Return New Vector2(x, y)
    End Function

    ''' <summary>
    ''' Returns an array of Vector2 containing the verticies of a regular polygon with the specified number of sides and radius,
    ''' located at the specified position.
    ''' </summary>
    ''' <param name="radius">The radius of a circle circumscribing the regular polygon.</param>
    ''' <param name="sides">The number of sides in the regular polygon.</param>
    ''' <param name="position">The center position of the polygon.</param>
    ''' <returns>The Vector2 array of polygon verticies.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetPolygonPoints(ByVal radius As Single, ByVal sides As Integer, ByVal position As Vector2) As Vector2()
        Dim points(sides - 1) As Vector2
        For side As Integer = 0 To sides - 1
            Dim delta As Single = PI / sides + side * 2 * PI / sides
            points(side) = New Vector2(position.X + radius * CSng(Math.Sin(delta)), position.Y + radius * CSng(Math.Cos(delta)))
        Next
        Return points
    End Function

    ''' <summary>
    ''' Returns an array of PointF containing the verticies of a regular polygon with the specified number of sides and radius,
    ''' located at the specified position.
    ''' </summary>
    ''' <param name="radius">The radius of a circle circumscribing the regular polygon.</param>
    ''' <param name="sides">The number of sides in the regular polygon.</param>
    ''' <param name="position">The center position of the polygon.</param>
    ''' <returns>The PointF array of polygon verticies.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetPolygonPoints(ByVal radius As Single, ByVal sides As Integer, ByVal position As PointF) As PointF()
        Dim points(sides - 1) As PointF
        For side As Integer = 0 To sides - 1
            Dim delta As Single = PI / sides + side * 2 * PI / sides
            points(side) = New PointF(position.X + radius * CSng(Math.Sin(delta)), position.Y + radius * CSng(Math.Cos(delta)))
        Next
        Return points
    End Function

    ''' <summary>
    ''' Calculates a vector along a parabola over time from an initial location, speed, and angle with the specified gravity.
    ''' For example, calculates the path of a ball thrown at an angle.
    ''' </summary>
    ''' <param name="startPoint">The initial vector from which the projectile begins.</param>
    ''' <param name="initialSpeed">The initial speed of the projectile.</param>
    ''' <param name="initialAngle">The angle at which the projectile is fired. An angle of 0 is to the right, with positive angles toward the top.</param>
    ''' <param name="gravity">The speed at which the projectile's y axis is modified over time.</param>
    ''' <param name="time">The point in time along the curve from which the position is taken.</param>
    ''' <returns>A Vector2 position located along the parabolic curve resulting from the specified parameters.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetProjectilePosition(startPoint As Vector2, initialSpeed As Single, initialAngle As Single, gravity As Single, time As Single) As Vector2
        Dim x, y As Single
        x = startPoint.X + CSng(initialSpeed * Math.Cos(DegreesToRadians(initialAngle)) * time)
        y = startPoint.Y - CSng(initialSpeed * Math.Sin(DegreesToRadians(initialAngle)) * time + 0.5 * gravity * Math.Pow(time, 2))
        Return New Vector2(x, y)
    End Function

    ''' <summary>
    ''' Determines if the specified value is positive or negative infinity.
    ''' </summary>
    ''' <param name="value">The value to check.</param>
    ''' <returns>True if the value is infinite, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Shared Function IsInfinite(ByVal value As Single) As Boolean
        Return Single.IsInfinity(value)
    End Function

    ''' <summary>
    ''' Determines if either dimension of the specified vector is positive or negative infinity.
    ''' </summary>
    ''' <param name="value">The Vector2 to check.</param>
    ''' <returns>True if the vector is infinite, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Shared Function IsInfinite(ByVal value As Vector2) As Boolean
        Return Single.IsInfinity(value.X) OrElse Single.IsInfinity(value.Y)
    End Function

    ''' <summary>
    ''' Determines if the specified number is not infinity and is a real number.
    ''' </summary>
    ''' <param name="value">The value to check.</param>
    ''' <returns>True if value is a real number, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Shared Function IsRealNumber(ByVal value As Single) As Boolean
        If Single.IsNaN(value) Then Return False
        Return Not Single.IsInfinity(value)
    End Function

    ''' <summary>
    ''' Returns the greater of two values.
    ''' </summary>
    ''' <param name="value1">The first value to compare.</param>
    ''' <param name="value2">The second value to compare.</param>
    ''' <returns>The greater of the two values.</returns>
    ''' <remarks></remarks>
    Public Shared Function Max(ByVal value1 As Single, ByVal value2 As Single) As Single
        If value2 > value1 Then Return value2
        Return value1
    End Function

    ''' <summary>
    ''' Returns the lesser of two values.
    ''' </summary>
    ''' <param name="value1">The first value to compare.</param>
    ''' <param name="value2">The second value to compare.</param>
    ''' <returns>The lesser of the two values.</returns>
    ''' <remarks></remarks>
    Public Shared Function Min(ByVal value1 As Single, ByVal value2 As Single) As Single
        If value2 < value1 Then Return value2
        Return value1
    End Function

    ''' <summary>
    ''' Raises a value to the specified exponent.
    ''' </summary>
    ''' <param name="value">The value to raise.</param>
    ''' <param name="exponent">The exponent to raise by.</param>
    ''' <returns>The value raised to the exponent.</returns>
    ''' <remarks></remarks>
    Public Shared Function Pow(ByVal value As Single, ByVal exponent As Integer) As Single
        For count As Integer = 0 To exponent - 2
            value *= value
        Next
        Return value
    End Function

    ''' <summary>
    ''' Converts the specified angle from radians to degrees.
    ''' </summary>
    ''' <param name="radians">The angle in radians.</param>
    ''' <returns>The angle in degrees.</returns>
    ''' <remarks></remarks>
    Public Shared Function RadiansToDegrees(ByVal radians As Single) As Single
        Return CSng(radians * (HALF_CIRCLE / Math.PI))
    End Function

    ''' <summary>
    ''' Gets the Sine of an angle specified in radians.
    ''' </summary>
    ''' <param name="radians">The angle in radians.</param>
    ''' <returns>The Sine of the angle.</returns>
    ''' <remarks></remarks>
    Public Shared Function Sin(ByVal radians As Single) As Single
        Return CSng(Math.Sin(CDbl(radians)))
    End Function

    ''' <summary>
    ''' Gets the square-root of the specified value.
    ''' </summary>
    ''' <param name="value">The value to get the square-root of.</param>
    ''' <returns>The square-root of value.</returns>
    ''' <remarks></remarks>
    Public Shared Function Sqrt(ByVal value As Single) As Single
        Return CSng(CDbl(value) ^ 0.5)
    End Function

    ''' <summary>
    ''' Squares the specified value.
    ''' </summary>
    ''' <param name="value">The value to square.</param>
    ''' <returns>The square of value.</returns>
    ''' <remarks></remarks>
    Public Shared Function Square(ByVal value As Single) As Single
        Return value * value
    End Function

    ''' <summary>
    ''' Exchanges the value of two target System.Single values.
    ''' </summary>
    ''' <param name="value1">The first value to swap.</param>
    ''' <param name="value2">The second value to swap.</param>
    ''' <remarks></remarks>
    Public Shared Sub Swap(ByRef value1 As Single, ByRef value2 As Single)
        Dim temp As Single = value2
        value1 = value2
        value2 = temp
    End Sub

    ''' <summary>
    ''' Gets the Tangent of an angle specified in radians.
    ''' </summary>
    ''' <param name="radians">The angle in radians.</param>
    ''' <returns>The Tangent of the angle.</returns>
    ''' <remarks></remarks>
    Public Shared Function Tan(ByVal radians As Single) As Single
        Return CSng(Math.Tan(CDbl(radians)))
    End Function

    ''' <summary>
    ''' Determines if the specified value falls between the specified min and max, inclusive.
    ''' </summary>
    ''' <param name="value">The value to check.</param>
    ''' <param name="min">The minimum acceptable value, inclusive.</param>
    ''' <param name="max">The maximum acceptable value, inclusive.</param>
    ''' <returns>True if the value is between min and max, otherwise false.</returns>
    ''' <remarks></remarks>
    Public Shared Function WithinRange(ByVal value As Single, ByVal min As Single, ByVal max As Single) As Boolean
        Return value >= min AndAlso value <= max
    End Function

    ''' <summary>
    ''' Wraps an angle specified in radians to keep it within the circumference of a circle.
    ''' </summary>
    ''' <param name="radians">The amount of radians to wrap.</param>
    ''' <returns>The wrapped angle.</returns>
    ''' <remarks></remarks>
    Public Shared Function WrapAngle(ByVal radians As Single) As Single
        While radians < -Math.PI
            radians += TWO_PI
        End While
        While radians > Math.PI
            radians -= TWO_PI
        End While
        Return radians
    End Function

End Class
